﻿namespace WP7Contrib.View.Controls.Reader
{
    using System;
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Markup;
    using System.Collections.Specialized;
    using WP7Contrib.View.Controls.Extensions;
    using WP7Contrib.Views.Controls.Common;

    using Orientation = System.Windows.Controls.Orientation;

    [ContentPropertyAttribute("Items")]
    public class ReaderControl : ItemsControl
    {
        // child controls
        private const string LayoutRootName = "LayoutRoot";
        internal Panel LayoutRoot { get; set; }

        // scroll view
        private ReaderView scrollView;

        public ReaderView ScrollView
        {
            get
            {
                return this.scrollView;
            }
            set
            {
                this.scrollView = value;
            }
        }

        public ReaderControl()
        {
            // apply default style
            this.DefaultStyleKey = typeof(ReaderControl);

            // defaults selected item to none
            SelectedIndex = -1;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // get the parts
            LayoutRoot = GetTemplateChild(LayoutRootName) as Panel;

            // scroll view
            this.scrollView = new ReaderView(this);
            this.scrollView.ScrollCompleted += new ScrollCompletedEventHandler(ScrollView_ScrollCompleted);

            // control events
            SizeChanged += new SizeChangedEventHandler(OnSizeChanged);
        }

        void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            // clip to control layout
            LayoutRoot.SetValue(Panel.ClipProperty, new RectangleGeometry() { Rect = new Rect(0, 0, this.ActualWidth, this.ActualHeight) });

            // reset scroll viewer
            Dispatcher.BeginInvoke(() =>
            {
                this.scrollView.Invalidate(false);
            });
        }

        public void Invalidate()
        {
            this.scrollView.Invalidate();
        }

        #region Navigation
        public void ScrollPrev()
        {
            if (null != this.scrollView)
            {
                // complete current animation
                this.scrollView.ScrollSkip();

                // move to previous item
                this.scrollView.ScrollTo(this.SelectedIndex - 1);
            }
        }

        public void ScrollNext()
        {
            if (null != this.scrollView)
            {
                // complete current animation
                this.scrollView.ScrollSkip();

                // move to next item
                this.scrollView.ScrollTo(this.SelectedIndex + 1);
            }
        }

        private void MoveTo(int index)
        {
            if (null != this.scrollView)
            {
                // complete current animation
                this.scrollView.ScrollSkip();

                // move to item
                this.scrollView.MoveTo(index);
            }
        }

        void ScrollView_ScrollCompleted(object sender, ScrollCompletedEventArgs e)
        {
           // find out where we landed
            SelectedIndex = e.SelectedIndex;

            // special case for when we only have 1 item :
            // the above code will not trigger the SelectedIndex change
            // since we'll be staying on item(0).
            if (this.Items.Count == 1)
            {
                // reset visuals
                MoveTo(SelectedIndex);
            }
        }
        #endregion

        #region Keyboard events
        protected override void OnKeyUp(KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Up:
                case Key.Left:
                    ScrollPrev();
                    break;
                case Key.Down:
                case Key.Right:
                    ScrollNext();
                    break;
            }
        }
        #endregion

        #region Multitouch events
#if WINDOWS_PHONE
        ManipulationHook _hook = new ManipulationHook();
        ManipulationTracker _tracker;
        protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
        {
            // manipulation events hook
            _hook.Hook(e.ManipulationContainer);

            // some controls just swallow the manipulation changes events
            // this is true with the ScrollViewer inside a ListBox.
            // register to ManipulationDelta of the source object
            // so we receive this event regardless of the parent implementation.
            _hook.HookDeltaHandler(OnManipulationDelta);

            // skip current animation (if any)
            this.scrollView.ScrollSkip();

            // start tracking and capture starting position
            _tracker = new ManipulationTracker(Orientation.Horizontal);
            _tracker.StartTracking(new Point(this.scrollView.Position, 0.0));
        }

        protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
        {
            // already handled ?
            if (e.Handled) return;

            // manipulation canceled
            if (_tracker.Canceled) return;

            if (_tracker.TrackManipulation(e.CumulativeManipulation.Translation))
            {
                // mark as handled to stop
                // underlying control's manipulations
                e.Handled = true;

                // cancel capture from current object to disable click behavior,
                // if we've started scrolling. Not sure what the best technique is.
                // let's just hook/override OnManipulationCompleted
                // and force e.Handled on it for now...
                _hook.HookCompletedHandler(OnManipulationCompleted);

                // move to position
                this.scrollView.Position = _tracker.Position.X;
            }

            // complete manipulation
            if (_tracker.Completed) e.Complete();
        }

        protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
        {
            // manipulation canceled
            if (_tracker.Canceled) return;

            // end tracking
            // BUGBUG : disable due to empty e.TotalManipulation in #6326
            // _tracker.TrackManipulation(e.TotalManipulation.Translation);

            // get direction
            int direction = (int)Math.Sign(-e.FinalVelocities.LinearVelocity.X);

            // move previous/next
            switch (direction)
            {
                case -1:
                    ScrollPrev();
                    return;
                case 1:
                    ScrollNext();
                    return;
            }

            // find out which item is at screen center
            double center = _tracker.Position.X + LayoutRoot.ActualWidth / 2;

            // cycle back to last item ?
            if (center < 0)
            {
                ScrollPrev();
                return;
            }

            // cycle back to first item ?
            if (center > this.Items.GetTotalWidth())
            {
                ScrollNext();
                return;
            }

            // item's start position
            int index = this.Items.GetIndexOfPosition(center);
            ReaderItem item = (ReaderItem)this.Items.GetItem(index);
            double start = this.Items.GetItemPosition(index);

            // close to left edge : snap left
            if (center - start < LayoutRoot.ActualWidth / 2)
            {
                this.scrollView.ScrollTo(index, Snapping.SnapLeft);
            }

            // close to right edge : snap right
            else if (center - start > item.Width - LayoutRoot.ActualWidth / 2)
            {
                double end = start + item.Width;
                this.scrollView.ScrollTo(index, Snapping.SnapRight);
            }

            // nowhere close to edges but
            // we're asked to snap anyways
            else if (item.AutoSnap)
            {
                this.scrollView.ScrollTo(index);
            }
        }
#endif
        #endregion

        #region DefaultItemWidth Property
        public static readonly DependencyProperty DefaultItemWidthProperty = DependencyProperty.Register(
            "DefaultItemWidth",
            typeof(double),
            typeof(ReaderControl),
            new PropertyMetadata(OnDefaultItemWidthChanged));

        public double DefaultItemWidth
        {
            get { return (double)GetValue(DefaultItemWidthProperty); }
            set { SetValue(DefaultItemWidthProperty, value); }
        }

        private static void OnDefaultItemWidthChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            ReaderControl ctrl = (ReaderControl)sender;
            ctrl.UpdateItemWidthAndHeight();
        }
        #endregion

        #region DefaultItemHeight Property
        public static readonly DependencyProperty DefaultItemHeightProperty = DependencyProperty.Register(
            "DefaultItemHeight",
            typeof(double),
            typeof(ReaderControl),
            new PropertyMetadata(OnDefaultItemHeightChanged));

        public double DefaultItemHeight
        {
            get { return (double)GetValue(DefaultItemHeightProperty); }
            set { SetValue(DefaultItemHeightProperty, value); }
        }

        private static void OnDefaultItemHeightChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            ReaderControl ctrl = (ReaderControl)sender;
            ctrl.UpdateItemWidthAndHeight();
        }
        #endregion

        #region Items
        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnItemsChanged(e);

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                case NotifyCollectionChangedAction.Reset:
                case NotifyCollectionChangedAction.Replace:
                    // fixup all items default width and height
                    UpdateItemWidthAndHeight();
                    break;
                case NotifyCollectionChangedAction.Remove:
                    break;
            }

            // invalidate the control
            if (null != this.scrollView)
                this.scrollView.Invalidate();

            // first inserted ?
            if ((SelectedIndex < 0) && (Items.Count > 0))
                SelectedIndex = 0;

            // invalid selected item ?
            if (!Items.Contains(SelectedItem) && (Items.Count > 0))
                SelectedIndex = 0;
        }

        private void UpdateItemWidthAndHeight()
        {
            foreach (object o in this.Items)
            {
                ReaderItem item = o as ReaderItem;
                if (null != item)
                {
                    // reset width for each item
                    double width = (double)item.GetValue(ReaderItem.WidthProperty);
                    if (double.IsNaN(width)) width = 0;
                    item.SetValue(ReaderItem.WidthProperty, Math.Max(width, DefaultItemWidth));

                    // reset height for each item
                    double height = (double)item.GetValue(ReaderItem.HeightProperty);
                    if (double.IsNaN(height)) height = 0;
                    item.SetValue(ReaderItem.HeightProperty, Math.Max(height, DefaultItemHeight));
                }
            }
        }
        #endregion

        #region SelectedItem
        public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
            "SelectedItem",
            typeof(ReaderItem),
            typeof(ReaderControl),
            new PropertyMetadata(OnSelectedItemChanged));

        public ReaderItem SelectedItem
        {
            get { return (ReaderItem)GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value); }
        }

        private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // this is a wrapper method for selection changes
            // the real work occurs in OnSelectedIndexChanges

            ReaderControl ctrl = (ReaderControl)d;
            ReaderItem oldItem = (ReaderItem)e.OldValue;
            ReaderItem newItem = (ReaderItem)e.NewValue;

            // find out the index for new items
            int index = ctrl.Items.IndexOf(newItem);

            // none found or null : return to old
            if ((null == newItem) || (index == -1))
                index = ctrl.Items.IndexOf(oldItem);

            // change selection
            ctrl.SelectedIndex = index;
        }
        #endregion

        #region SelectedIndex
        public event SelectionChangedEventHandler SelectionChanged;

        public static readonly DependencyProperty SelectedIndexProperty = DependencyProperty.Register(
            "SelectedIndex",
            typeof(int),
            typeof(ReaderControl),
            new PropertyMetadata(OnSelectedIndexChanged));

        public int SelectedIndex
        {
            get { return (int)GetValue(SelectedIndexProperty); }
            set { SetValue(SelectedIndexProperty, value); }
        }

        private static void OnSelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ReaderControl ctrl = (ReaderControl)d;
            int oldIndex = (int)e.OldValue;
            int newIndex = (int)e.NewValue;

            // nothing to do
            if (newIndex == oldIndex)
            {
                // exit here, to avoid infinite recursion
                return;
            }

            // change selection
            ctrl.SelectedIndex = newIndex;
            ctrl.SelectedItem = (ReaderItem)ctrl.Items.GetItem(newIndex);

            // change visuals
            ctrl.MoveTo(ctrl.SelectedIndex);

            // raise event
            ReaderItem oldItem = (ReaderItem)ctrl.Items.GetItem(oldIndex);
            ReaderItem newItem = (ReaderItem)ctrl.Items.GetItem(newIndex);
            SelectionChangedEventArgs args = new SelectionChangedEventArgs(
                (null == oldItem) ? new List<ReaderItem> { } : new List<ReaderItem> { oldItem },
                (null == newItem) ? new List<ReaderItem> { } : new List<ReaderItem> { newItem });

            ctrl.OnSelectionChanged(args);
        }

        protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
        {
            if (null != SelectionChanged)
                SelectionChanged(this, args);
        }
        #endregion

        #region SliderSelectedIndex

        public static readonly DependencyProperty SliderSelectedIndexProperty = DependencyProperty.Register(
            "SliderSelectedIndex",
            typeof(int),
            typeof(ReaderControl),
            new PropertyMetadata(OnSliderSelectedIndexChanged));

        public int SliderSelectedIndex
        {
            get { return (int)GetValue(SliderSelectedIndexProperty); }
            set { SetValue(SliderSelectedIndexProperty, value); }
        }

        private static void OnSliderSelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ReaderControl ctrl = (ReaderControl)d;
            int oldIndex = (int)e.OldValue;
            int newIndex = (int)e.NewValue;

            // nothing to do
            if (newIndex == oldIndex)
            {
                // exit here, to avoid infinite recursion
                return;
            }

            // change selection
            ctrl.SelectedIndex = newIndex;
            ctrl.SelectedItem = (ReaderItem)ctrl.Items.GetItem(newIndex);

            // change visuals
           ctrl.ScrollView.ScrollTo(ctrl.SelectedIndex);
          

            // raise event
            ReaderItem oldItem = (ReaderItem)ctrl.Items.GetItem(oldIndex);
            ReaderItem newItem = (ReaderItem)ctrl.Items.GetItem(newIndex);
            SelectionChangedEventArgs args = new SelectionChangedEventArgs(
                (null == oldItem) ? new List<ReaderItem>() : new List<ReaderItem> { oldItem },
                (null == newItem) ? new List<ReaderItem>() : new List<ReaderItem> { newItem });

            ctrl.OnSelectionChanged(args);
        }

     
        #endregion

        #region Font Family (DependencyProperty)

       public FontFamily ReaderItemsFontFamily
        {
            get { return (FontFamily)GetValue(ReaderItemsFontFamilyProperty); }
            set { SetValue(ReaderItemsFontFamilyProperty, value); }
        }

        public static readonly DependencyProperty ReaderItemsFontFamilyProperty = DependencyProperty.Register(
            "ReaderItemsFontFamily",
            typeof(FontFamily),
            typeof(ReaderControl),
            new PropertyMetadata(new FontFamily("Segoe WP"), new PropertyChangedCallback(OnReaderItemsFontFamilyChanged)));

        private static void OnReaderItemsFontFamilyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ReaderControl)d).OnReaderItemsFontFamilyChanged(e);
        }

        protected virtual void OnReaderItemsFontFamilyChanged(DependencyPropertyChangedEventArgs e)
        {
            foreach (var item in Items)
            {
                if (null != item)
                {
                    ReaderItem readerItem = item as ReaderItem;

                    if (readerItem != null)
                    {
                        readerItem.FontFamily = (FontFamily)e.NewValue;
                    }
                }
            }
        }

        #endregion

        #region Font Size (DependencyProperty)

        public int ReaderItemsFontSize
        {
            get { return (int)GetValue(ReaderItemsFontSizeProperty); }
            set { SetValue(ReaderItemsFontSizeProperty, value); }
        }

        public static readonly DependencyProperty ReaderItemsFontSizeProperty = DependencyProperty.Register(
            "ReaderItemsFontSize",
            typeof(int),
            typeof(ReaderControl),
            new PropertyMetadata(24, new PropertyChangedCallback(OnReaderItemsFontSizeChanged)));

        private static void OnReaderItemsFontSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ReaderControl)d).OnReaderItemsFontSizeChanged(e);
        }

        protected virtual void OnReaderItemsFontSizeChanged(DependencyPropertyChangedEventArgs e)
        {
            foreach (var item in Items)
            {
                if (null != item)
                {
                    ReaderItem readerItem = item as ReaderItem;

                    if (readerItem != null)
                    {
                        readerItem.FontSize = (int)e.NewValue;
                    }
                }
            }
        }

        #endregion
    }
}
